﻿namespace Jsl.FxCop
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Xml.Linq;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class When_searching_breadth_first
    {
        private static readonly string complexXml = @"
<Root>
    <A>
        <A1>
            <A1a>
                <A1a1/>
            </A1a>
            <A1b/><A1c/>
        </A1>
        <A2>
            <A2a/>
        </A2>
    </A>
    <B>
        <B1>
            <B1a/><B1b/>
        </B1>
    </B>
    <C>
        <C1/><C2/><C3/>
    </C>
</Root>";

        [TestMethod]
        public void Empty_tree_works()
        {
            BreadthFirstSearch<int>.Search(
                Enumerable.Empty<int>(),
                item =>
                {
                    Assert.Fail("Should never get here.");
                    return SearchState.ContinueSearch;
                },
                item =>
                {
                    Assert.Fail("Should never get here.");
                    return Enumerable.Empty<int>();
                });
        }

        [TestMethod]
        public void Only_initial_item_is_visited_and_searched_if_there_are_no_descendants()
        {
            Test(
                "<A/>",
                item => SearchState.ContinueSearch,
                new string[] { "A" },
                new string[] { "A" });
        }

        [TestMethod]
        public void If_specified_duplicates_are_not_searched()
        {
            var itemsVisited = new List<int>();
            var childrenAskedFor = new List<int>();

            BreadthFirstSearch<int>.Search(
                new int[] { 1, 2, 3, 2 },
                item =>
                {
                    itemsVisited.Add(item);
                    return SearchState.ContinueSearch;
                },
                item =>
                {
                    childrenAskedFor.Add(item);
                    return Enumerable.Empty<int>();
                },
                true);

            AssertEx.That(() => itemsVisited.SequenceEqual(new int[] { 1, 2, 3 }));
            AssertEx.That(() => childrenAskedFor.SequenceEqual(new int[] { 1, 2, 3 }));
        }

        [TestMethod]
        public void Complex_trees_are_searched()
        {
            Test(
                complexXml,
                item => SearchState.ContinueSearch,
                new string[] { "Root", "A", "B", "C", "A1", "A2", "B1", "C1", "C2", "C3", "A1a", "A1b", "A1c", "A2a", "B1a", "B1b", "A1a1" },
                new string[] { "Root", "A", "B", "C", "A1", "A2", "B1", "C1", "C2", "C3", "A1a", "A1b", "A1c", "A2a", "B1a", "B1b", "A1a1" });
        }

        [TestMethod]
        public void Caller_can_ignore_descendants()
        {
            Test(
                complexXml,
                item => item.Length == 2 ? SearchState.SkipDescendants : SearchState.ContinueSearch,
                new string[] { "Root", "A", "B", "C", "A1", "A2", "B1", "C1", "C2", "C3" },
                new string[] { "Root", "A", "B", "C" });
        }

        [TestMethod]
        public void Call_can_cancel_search()
        {
            Test(
                complexXml,
                item => item == "A1a" ? SearchState.StopSearch : SearchState.ContinueSearch,
                new string[] { "Root", "A", "B", "C", "A1", "A2", "B1", "C1", "C2", "C3", "A1a" },
                new string[] { "Root", "A", "B", "C", "A1" });
        }

        #region Private Methods
        private static void Test(
            string xmlTree,
            Func<string, SearchState> itemVisitor,
            IEnumerable<string> expectedVisited = null,
            IEnumerable<string> expectedItemsQueried = null)
        {
            var itemsVisited = new List<string>();
            var childrenAskedFor = new List<string>();

            var xmlTreeDocument = XDocument.Parse(xmlTree);
            BreadthFirstSearch<XElement>.Search(
                xmlTreeDocument.Root,
                item =>
                {
                    itemsVisited.Add(item.Name.LocalName);
                    return itemVisitor(item.Name.LocalName);
                },
                item =>
                {
                    childrenAskedFor.Add(item.Name.LocalName);
                    return item.Elements();
                });

            if (expectedVisited != null)
            {
                AssertEx.That(() => itemsVisited.SequenceEqual(expectedVisited));
            }

            if (expectedItemsQueried != null)
            {
                AssertEx.That(() => childrenAskedFor.SequenceEqual(expectedItemsQueried));
            }
        }
        #endregion Private Methods
    }
}